'use strict';

var primordials = {};

/* eslint-disable node-core/prefer-primordials */

// This file subclasses and stores the JS builtins that come from the VM
// so that Node.js's builtin modules do not need to later look these up from
// the global proxy, which can be mutated by users.

// Use of primordials have sometimes a dramatic impact on performance, please
// benchmark all changes made in performance-sensitive areas of the codebase.
// See: https://github.com/nodejs/node/pull/38248

const {
    defineProperty: ReflectDefineProperty,
    getOwnPropertyDescriptor: ReflectGetOwnPropertyDescriptor,
    ownKeys: ReflectOwnKeys,
} = Reflect;

// `uncurryThis` is equivalent to `func => Function.prototype.call.bind(func)`.
// It is using `bind.bind(call)` to avoid using `Function.prototype.bind`
// and `Function.prototype.call` after it may have been mutated by users.
const { apply, bind, call } = Function.prototype;
const uncurryThis = bind.bind(call);
primordials.uncurryThis = uncurryThis;

// `applyBind` is equivalent to `func => Function.prototype.apply.bind(func)`.
// It is using `bind.bind(apply)` to avoid using `Function.prototype.bind`
// and `Function.prototype.apply` after it may have been mutated by users.
const applyBind = bind.bind(apply);
primordials.applyBind = applyBind;

// Methods that accept a variable number of arguments, and thus it's useful to
// also create `${prefix}${key}Apply`, which uses `Function.prototype.apply`,
// instead of `Function.prototype.call`, and thus doesn't require iterator
// destructuring.
const varargsMethods = [
    // 'ArrayPrototypeConcat' is omitted, because it performs the spread
    // on its own for arrays and array-likes with a truthy
    // @@isConcatSpreadable symbol property.
    'ArrayOf',
    'ArrayPrototypePush',
    'ArrayPrototypeUnshift',
    // 'FunctionPrototypeCall' is omitted, since there's 'ReflectApply'
    // and 'FunctionPrototypeApply'.
    'MathHypot',
    'MathMax',
    'MathMin',
    'StringFromCharCode',
    'StringFromCodePoint',
    'StringPrototypeConcat',
    'TypedArrayOf',
];

function getNewKey(key) {
    return typeof key === 'symbol' ?
        `Symbol${key.description[7].toUpperCase()}${key.description.slice(8)}` :
        `${key[0].toUpperCase()}${key.slice(1)}`;
}

function copyAccessor(dest, prefix, key, { enumerable, get, set }) {
    ReflectDefineProperty(dest, `${prefix}Get${key}`, {
        __proto__: null,
        value: uncurryThis(get),
        enumerable
    });
    if (set !== undefined) {
        ReflectDefineProperty(dest, `${prefix}Set${key}`, {
            __proto__: null,
            value: uncurryThis(set),
            enumerable
        });
    }
}

function copyPropsRenamed(src, dest, prefix) {
    for (const key of ReflectOwnKeys(src)) {
        const newKey = getNewKey(key);
        const desc = ReflectGetOwnPropertyDescriptor(src, key);
        if ('get' in desc) {
            copyAccessor(dest, prefix, newKey, desc);
        } else {
            const name = `${prefix}${newKey}`;
            ReflectDefineProperty(dest, name, { __proto__: null, ...desc });
            if (varargsMethods.includes(name)) {
                ReflectDefineProperty(dest, `${name}Apply`, {
                    __proto__: null,
                    // `src` is bound as the `this` so that the static `this` points
                    // to the object it was defined on,
                    // e.g.: `ArrayOfApply` gets a `this` of `Array`:
                    value: applyBind(desc.value, src),
                });
            }
        }
    }
}

function copyPropsRenamedBound(src, dest, prefix) {
    for (const key of ReflectOwnKeys(src)) {
        const newKey = getNewKey(key);
        const desc = ReflectGetOwnPropertyDescriptor(src, key);
        if ('get' in desc) {
            copyAccessor(dest, prefix, newKey, desc);
        } else {
            const { value } = desc;
            if (typeof value === 'function') {
                desc.value = value.bind(src);
            }

            const name = `${prefix}${newKey}`;
            ReflectDefineProperty(dest, name, { __proto__: null, ...desc });
            if (varargsMethods.includes(name)) {
                ReflectDefineProperty(dest, `${name}Apply`, {
                    __proto__: null,
                    value: applyBind(value, src),
                });
            }
        }
    }
}

function copyPrototype(src, dest, prefix) {
    for (const key of ReflectOwnKeys(src)) {
        const newKey = getNewKey(key);
        const desc = ReflectGetOwnPropertyDescriptor(src, key);
        if ('get' in desc) {
            copyAccessor(dest, prefix, newKey, desc);
        } else {
            const { value } = desc;
            if (typeof value === 'function') {
                desc.value = uncurryThis(value);
            }

            const name = `${prefix}${newKey}`;
            ReflectDefineProperty(dest, name, { __proto__: null, ...desc });
            if (varargsMethods.includes(name)) {
                ReflectDefineProperty(dest, `${name}Apply`, {
                    __proto__: null,
                    value: applyBind(value),
                });
            }
        }
    }
}

// Create copies of configurable value properties of the global object
[
    'Proxy',
    'globalThis',
].forEach((name) => {
    // eslint-disable-next-line no-restricted-globals
    primordials[name] = globalThis[name];
});

// Create copies of URI handling functions
[
    decodeURI,
    decodeURIComponent,
    encodeURI,
    encodeURIComponent,
].forEach((fn) => {
    primordials[fn.name] = fn;
});

// Create copies of legacy functions
[
    escape,
    eval,
    unescape,
].forEach((fn) => {
    primordials[fn.name] = fn;
});

// Create copies of the namespace objects
[
    'JSON',
    'Math',
    'Proxy',
    'Reflect',
].forEach((name) => {
    // eslint-disable-next-line no-restricted-globals
    copyPropsRenamed(globalThis[name], primordials, name);
});

// Create copies of intrinsic objects
[
    'AggregateError',
    'Array',
    'ArrayBuffer',
    'BigInt',
    'BigInt64Array',
    'BigUint64Array',
    'Boolean',
    'DataView',
    'Date',
    'Error',
    'EvalError',
    'FinalizationRegistry',
    'Float32Array',
    'Float64Array',
    'Function',
    'Int16Array',
    'Int32Array',
    'Int8Array',
    'Map',
    'Number',
    'Object',
    'RangeError',
    'ReferenceError',
    'RegExp',
    'Set',
    'String',
    'Symbol',
    'SyntaxError',
    'TypeError',
    'URIError',
    'Uint16Array',
    'Uint32Array',
    'Uint8Array',
    'Uint8ClampedArray',
    'WeakMap',
    'WeakRef',
    'WeakSet',
].forEach((name) => {
    // eslint-disable-next-line no-restricted-globals
    const original = globalThis[name];
    primordials[name] = original;
    copyPropsRenamed(original, primordials, name);
    copyPrototype(original.prototype, primordials, `${name}Prototype`);
});

// Create copies of intrinsic objects that require a valid `this` to call
// static methods.
// Refs: https://www.ecma-international.org/ecma-262/#sec-promise.all
[
    'Promise',
].forEach((name) => {
    // eslint-disable-next-line no-restricted-globals
    const original = globalThis[name];
    primordials[name] = original;
    copyPropsRenamedBound(original, primordials, name);
    copyPrototype(original.prototype, primordials, `${name}Prototype`);
});

// Create copies of abstract intrinsic objects that are not directly exposed
// on the global object.
// Refs: https://tc39.es/ecma262/#sec-%typedarray%-intrinsic-object
[
    { name: 'TypedArray', original: Reflect.getPrototypeOf(Uint8Array) },
    {
        name: 'ArrayIterator', original: {
            prototype: Reflect.getPrototypeOf(Array.prototype[Symbol.iterator]()),
        }
    },
    {
        name: 'StringIterator', original: {
            prototype: Reflect.getPrototypeOf(String.prototype[Symbol.iterator]()),
        }
    },
].forEach(({ name, original }) => {
    primordials[name] = original;
    // The static %TypedArray% methods require a valid `this`, but can't be bound,
    // as they need a subclass constructor as the receiver:
    copyPrototype(original, primordials, name);
    copyPrototype(original.prototype, primordials, `${name}Prototype`);
});

/* eslint-enable node-core/prefer-primordials */

const {
    Array: ArrayConstructor,
    ArrayPrototypeForEach,
    ArrayPrototypeMap,
    FinalizationRegistry,
    FunctionPrototypeCall,
    Map,
    ObjectDefineProperties,
    ObjectDefineProperty,
    ObjectFreeze,
    ObjectSetPrototypeOf,
    Promise,
    PromisePrototypeThen,
    PromiseResolve,
    ReflectApply,
    ReflectConstruct,
    ReflectSet,
    ReflectGet,
    RegExp,
    RegExpPrototype,
    RegExpPrototypeExec,
    RegExpPrototypeGetDotAll,
    RegExpPrototypeGetFlags,
    RegExpPrototypeGetGlobal,
    RegExpPrototypeGetHasIndices,
    RegExpPrototypeGetIgnoreCase,
    RegExpPrototypeGetMultiline,
    RegExpPrototypeGetSource,
    RegExpPrototypeGetSticky,
    RegExpPrototypeGetUnicode,
    Set,
    SymbolIterator,
    SymbolMatch,
    SymbolMatchAll,
    SymbolReplace,
    SymbolSearch,
    SymbolSpecies,
    SymbolSplit,
    WeakMap,
    WeakRef,
    WeakSet,
} = primordials;

// Because these functions are used by `makeSafe`, which is exposed
// on the `primordials` object, it's important to use const references
// to the primordials that they use:
const createSafeIterator = (factory, next) => {
    class SafeIterator {
        constructor(iterable) {
            this._iterator = factory(iterable);
        }
        next() {
            return next(this._iterator);
        }
        [SymbolIterator]() {
            return this;
        }
    }
    ObjectSetPrototypeOf(SafeIterator.prototype, null);
    ObjectFreeze(SafeIterator.prototype);
    ObjectFreeze(SafeIterator);
    return SafeIterator;
};

primordials.SafeArrayIterator = createSafeIterator(
    primordials.ArrayPrototypeSymbolIterator,
    primordials.ArrayIteratorPrototypeNext
);
primordials.SafeStringIterator = createSafeIterator(
    primordials.StringPrototypeSymbolIterator,
    primordials.StringIteratorPrototypeNext
);

const copyProps = (src, dest) => {
    ArrayPrototypeForEach(ReflectOwnKeys(src), (key) => {
        if (!ReflectGetOwnPropertyDescriptor(dest, key)) {
            ReflectDefineProperty(
                dest,
                key,
                { __proto__: null, ...ReflectGetOwnPropertyDescriptor(src, key) });
        }
    });
};

/**
 * @type {typeof primordials.makeSafe}
 */
const makeSafe = (unsafe, safe) => {
    if (SymbolIterator in unsafe.prototype) {
        const dummy = new unsafe();
        let next; // We can reuse the same `next` method.

        ArrayPrototypeForEach(ReflectOwnKeys(unsafe.prototype), (key) => {
            if (!ReflectGetOwnPropertyDescriptor(safe.prototype, key)) {
                const desc = ReflectGetOwnPropertyDescriptor(unsafe.prototype, key);
                if (
                    typeof desc.value === 'function' &&
                    desc.value.length === 0 &&
                    SymbolIterator in (FunctionPrototypeCall(desc.value, dummy) ?? {})
                ) {
                    const createIterator = uncurryThis(desc.value);
                    next ??= uncurryThis(createIterator(dummy).next);
                    const SafeIterator = createSafeIterator(createIterator, next);
                    desc.value = function () {
                        return new SafeIterator(this);
                    };
                }
                ReflectDefineProperty(safe.prototype, key, { __proto__: null, ...desc });
            }
        });
    } else {
        copyProps(unsafe.prototype, safe.prototype);
    }
    copyProps(unsafe, safe);

    ObjectSetPrototypeOf(safe.prototype, null);
    ObjectFreeze(safe.prototype);
    ObjectFreeze(safe);
    return safe;
};
primordials.makeSafe = makeSafe;

// Subclass the constructors because we need to use their prototype
// methods later.
// Defining the `constructor` is necessary here to avoid the default
// constructor which uses the user-mutable `%ArrayIteratorPrototype%.next`.
primordials.SafeMap = makeSafe(
    Map,
    class SafeMap extends Map {
        constructor(i) { super(i); } // eslint-disable-line no-useless-constructor
    }
);
primordials.SafeWeakMap = makeSafe(
    WeakMap,
    class SafeWeakMap extends WeakMap {
        constructor(i) { super(i); } // eslint-disable-line no-useless-constructor
    }
);

primordials.SafeSet = makeSafe(
    Set,
    class SafeSet extends Set {
        constructor(i) { super(i); } // eslint-disable-line no-useless-constructor
    }
);
primordials.SafeWeakSet = makeSafe(
    WeakSet,
    class SafeWeakSet extends WeakSet {
        constructor(i) { super(i); } // eslint-disable-line no-useless-constructor
    }
);

primordials.SafeFinalizationRegistry = makeSafe(
    FinalizationRegistry,
    class SafeFinalizationRegistry extends FinalizationRegistry {
        // eslint-disable-next-line no-useless-constructor
        constructor(cleanupCallback) { super(cleanupCallback); }
    }
);
primordials.SafeWeakRef = makeSafe(
    WeakRef,
    class SafeWeakRef extends WeakRef {
        // eslint-disable-next-line no-useless-constructor
        constructor(target) { super(target); }
    }
);

const SafePromise = makeSafe(
    Promise,
    class SafePromise extends Promise {
        // eslint-disable-next-line no-useless-constructor
        constructor(executor) { super(executor); }
    }
);

/**
 * Attaches a callback that is invoked when the Promise is settled (fulfilled or
 * rejected). The resolved value cannot be modified from the callback.
 * Prefer using async functions when possible.
 * @param {Promise<any>} thisPromise
 * @param {() => void) | undefined | null} onFinally The callback to execute
 *        when the Promise is settled (fulfilled or rejected).
 * @returns {Promise} A Promise for the completion of the callback.
 */
primordials.SafePromisePrototypeFinally = (thisPromise, onFinally) =>
    // Wrapping on a new Promise is necessary to not expose the SafePromise
    // prototype to user-land.
    new Promise((a, b) =>
        new SafePromise((a, b) => PromisePrototypeThen(thisPromise, a, b))
            .finally(onFinally)
            .then(a, b)
    );

primordials.AsyncIteratorPrototype =
    primordials.ReflectGetPrototypeOf(
        primordials.ReflectGetPrototypeOf(
            async function* () { }).prototype);

const arrayToSafePromiseIterable = (promises, mapFn) =>
    new primordials.SafeArrayIterator(
        ArrayPrototypeMap(
            promises,
            (promise, i) =>
                new SafePromise((a, b) => PromisePrototypeThen(mapFn == null ? promise : mapFn(promise, i), a, b))
        )
    );

/**
 * @template T,U
 * @param {Array<T | PromiseLike<T>>} promises
 * @param {(v: T|PromiseLike<T>, k: number) => U|PromiseLike<U>} [mapFn]
 * @returns {Promise<Awaited<U>[]>}
 */
primordials.SafePromiseAll = (promises, mapFn) =>
    // Wrapping on a new Promise is necessary to not expose the SafePromise
    // prototype to user-land.
    new Promise((a, b) =>
        SafePromise.all(arrayToSafePromiseIterable(promises, mapFn)).then(a, b)
    );

/**
 * Should only be used for internal functions, this would produce similar
 * results as `Promise.all` but without prototype pollution, and the return
 * value is not a genuine Array but an array-like object.
 * @template T,U
 * @param {ArrayLike<T | PromiseLike<T>>} promises
 * @param {(v: T|PromiseLike<T>, k: number) => U|PromiseLike<U>} [mapFn]
 * @returns {Promise<ArrayLike<Awaited<U>>>}
 */
primordials.SafePromiseAllReturnArrayLike = (promises, mapFn) =>
    new Promise((resolve, reject) => {
        const { length } = promises;

        const returnVal = ArrayConstructor(length);
        ObjectSetPrototypeOf(returnVal, null);
        if (length === 0) resolve(returnVal);

        let pendingPromises = length;
        for (let i = 0; i < length; i++) {
            const promise = mapFn != null ? mapFn(promises[i], i) : promises[i];
            PromisePrototypeThen(PromiseResolve(promise), (result) => {
                returnVal[i] = result;
                if (--pendingPromises === 0) resolve(returnVal);
            }, reject);
        }
    });

/**
 * Should only be used when we only care about waiting for all the promises to
 * resolve, not what value they resolve to.
 * @template T,U
 * @param {ArrayLike<T | PromiseLike<T>>} promises
 * @param {(v: T|PromiseLike<T>, k: number) => U|PromiseLike<U>} [mapFn]
 * @returns {Promise<void>}
 */
primordials.SafePromiseAllReturnVoid = (promises, mapFn) =>
    new Promise((resolve, reject) => {
        let pendingPromises = promises.length;
        if (pendingPromises === 0) resolve();
        for (let i = 0; i < promises.length; i++) {
            const promise = mapFn != null ? mapFn(promises[i], i) : promises[i];
            PromisePrototypeThen(PromiseResolve(promise), () => {
                if (--pendingPromises === 0) resolve();
            }, reject);
        }
    });

/**
 * @template T,U
 * @param {Array<T|PromiseLike<T>>} promises
 * @param {(v: T|PromiseLike<T>, k: number) => U|PromiseLike<U>} [mapFn]
 * @returns {Promise<PromiseSettledResult<any>[]>}
 */
primordials.SafePromiseAllSettled = (promises, mapFn) =>
    // Wrapping on a new Promise is necessary to not expose the SafePromise
    // prototype to user-land.
    new Promise((a, b) =>
        SafePromise.allSettled(arrayToSafePromiseIterable(promises, mapFn)).then(a, b)
    );

/**
 * Should only be used when we only care about waiting for all the promises to
 * settle, not what value they resolve or reject to.
 * @template T,U
 * @param {ArrayLike<T|PromiseLike<T>>} promises
 * @param {(v: T|PromiseLike<T>, k: number) => U|PromiseLike<U>} [mapFn]
 * @returns {Promise<void>}
 */
primordials.SafePromiseAllSettledReturnVoid = async (promises, mapFn) => {
    for (let i = 0; i < promises.length; i++) {
        try {
            await (mapFn != null ? mapFn(promises[i], i) : promises[i]);
        } catch {
            // In all settled, we can ignore errors.
        }
    }
};

/**
 * @template T,U
 * @param {Array<T|PromiseLike<T>>} promises
 * @param {(v: T|PromiseLike<T>, k: number) => U|PromiseLike<U>} [mapFn]
 * @returns {Promise<Awaited<U>>}
 */
primordials.SafePromiseAny = (promises, mapFn) =>
    // Wrapping on a new Promise is necessary to not expose the SafePromise
    // prototype to user-land.
    new Promise((a, b) =>
        SafePromise.any(arrayToSafePromiseIterable(promises, mapFn)).then(a, b)
    );

/**
 * @template T,U
 * @param {Array<T|PromiseLike<T>>} promises
 * @param {(v: T|PromiseLike<T>, k: number) => U|PromiseLike<U>} [mapFn]
 * @returns {Promise<Awaited<U>>}
 */
primordials.SafePromiseRace = (promises, mapFn) =>
    // Wrapping on a new Promise is necessary to not expose the SafePromise
    // prototype to user-land.
    new Promise((a, b) =>
        SafePromise.race(arrayToSafePromiseIterable(promises, mapFn)).then(a, b)
    );


const {
    exec: OriginalRegExpPrototypeExec,
    [SymbolMatch]: OriginalRegExpPrototypeSymbolMatch,
    [SymbolMatchAll]: OriginalRegExpPrototypeSymbolMatchAll,
    [SymbolReplace]: OriginalRegExpPrototypeSymbolReplace,
    [SymbolSearch]: OriginalRegExpPrototypeSymbolSearch,
    [SymbolSplit]: OriginalRegExpPrototypeSymbolSplit,
} = RegExpPrototype;

class RegExpLikeForStringSplitting {
    #regex;
    constructor() {
        this.#regex = ReflectConstruct(RegExp, arguments);
    }

    get lastIndex() {
        return ReflectGet(this.#regex, 'lastIndex');
    }
    set lastIndex(value) {
        ReflectSet(this.#regex, 'lastIndex', value);
    }

    exec() {
        return ReflectApply(OriginalRegExpPrototypeExec, this.#regex, arguments);
    }
}
ObjectSetPrototypeOf(RegExpLikeForStringSplitting.prototype, null);

primordials.hardenRegExp = function hardenRegExp(pattern) {
    ObjectDefineProperties(pattern, {
        [SymbolMatch]: {
            __proto__: null,
            configurable: true,
            value: OriginalRegExpPrototypeSymbolMatch,
        },
        [SymbolMatchAll]: {
            __proto__: null,
            configurable: true,
            value: OriginalRegExpPrototypeSymbolMatchAll,
        },
        [SymbolReplace]: {
            __proto__: null,
            configurable: true,
            value: OriginalRegExpPrototypeSymbolReplace,
        },
        [SymbolSearch]: {
            __proto__: null,
            configurable: true,
            value: OriginalRegExpPrototypeSymbolSearch,
        },
        [SymbolSplit]: {
            __proto__: null,
            configurable: true,
            value: OriginalRegExpPrototypeSymbolSplit,
        },
        constructor: {
            __proto__: null,
            configurable: true,
            value: {
                [SymbolSpecies]: RegExpLikeForStringSplitting,
            }
        },
        dotAll: {
            __proto__: null,
            configurable: true,
            value: RegExpPrototypeGetDotAll(pattern),
        },
        exec: {
            __proto__: null,
            configurable: true,
            value: OriginalRegExpPrototypeExec,
        },
        global: {
            __proto__: null,
            configurable: true,
            value: RegExpPrototypeGetGlobal(pattern),
        },
        hasIndices: {
            __proto__: null,
            configurable: true,
            value: RegExpPrototypeGetHasIndices(pattern),
        },
        ignoreCase: {
            __proto__: null,
            configurable: true,
            value: RegExpPrototypeGetIgnoreCase(pattern),
        },
        multiline: {
            __proto__: null,
            configurable: true,
            value: RegExpPrototypeGetMultiline(pattern),
        },
        source: {
            __proto__: null,
            configurable: true,
            value: RegExpPrototypeGetSource(pattern),
        },
        sticky: {
            __proto__: null,
            configurable: true,
            value: RegExpPrototypeGetSticky(pattern),
        },
        unicode: {
            __proto__: null,
            configurable: true,
            value: RegExpPrototypeGetUnicode(pattern),
        },
    });
    ObjectDefineProperty(pattern, 'flags', {
        __proto__: null,
        configurable: true,
        value: RegExpPrototypeGetFlags(pattern),
    });
    return pattern;
};


primordials.SafeStringPrototypeSearch = (str, regexp) => {
    regexp.lastIndex = 0;
    const match = RegExpPrototypeExec(regexp, str);
    return match ? match.index : -1;
};

ObjectSetPrototypeOf(primordials, null);
ObjectFreeze(primordials);

module.exports = primordials;
